{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TBFXQGKYUc4X"
      },
      "source": [
        "##### Copyright 2018 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "1z4xy2gTUc4a"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FE7KNzPPVrVV"
      },
      "source": [
        "# 이미지 분류"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "KwQtSOz0VrVX"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/images/classification\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/tutorials/images/classification.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/tutorials/images/classification.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서 소스 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/tutorials/images/classification.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gN7G9GFmVrVY"
      },
      "source": [
        "이 튜토리얼은 꽃 이미지를 분류하는 방법을 보여줍니다. `keras.Sequential` 모델을 사용하여 이미지 분류자를 만들고 `preprocessing.image_dataset_from_directory`를 사용하여 데이터를 로드합니다. 이를 통해 다음 개념을 실제로 경험해 볼 수 있습니다.\n",
        "\n",
        "- 디스크에서 데이터세트를 효율적으로 로드합니다.\n",
        "- 데이터 증강 및 드롭아웃을 포함하여 과대적합을 식별하고 이를 완화하는 기술을 적용합니다.\n",
        "\n",
        "이 튜토리얼은 기본적인 머신러닝 워크플로를 따릅니다.\n",
        "\n",
        "1. 데이터 검사 및 이해하기\n",
        "2. 입력 파이프라인 빌드하기\n",
        "3. 모델 빌드하기\n",
        "4. 모델 훈련하기\n",
        "5. 모델 테스트하기\n",
        "6. 모델을 개선하고 프로세스 반복하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "MQb2NgWuDgi5"
      },
      "outputs": [],
      "source": [
        "!pip install tf-nightly"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zF9uvbXNVrVY"
      },
      "source": [
        "## TensorFlow 및 기타 라이브러리 가져오기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "L1WtoaOHVrVh"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import os\n",
        "import PIL\n",
        "import tensorflow as tf\n",
        "\n",
        "from tensorflow import keras\n",
        "from tensorflow.keras import layers\n",
        "from tensorflow.keras.models import Sequential"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UZZI6lNkVrVm"
      },
      "source": [
        "## 데이터세트 다운로드 및 탐색하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DPHx8-t-VrVo"
      },
      "source": [
        "이 튜토리얼에서는 약 3,700장의 꽃 사진 데이터세트를 사용합니다. 데이터세트에는 클래스당 하나씩 5개의 하위 디렉토리가 있습니다.\n",
        "\n",
        "```\n",
        "flower_photo/\n",
        "  daisy/\n",
        "  dandelion/\n",
        "  roses/\n",
        "  sunflowers/\n",
        "  tulips/\n",
        "```"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "57CcilYSG0zv"
      },
      "outputs": [],
      "source": [
        "import pathlib\n",
        "dataset_url = \"https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz\"\n",
        "data_dir = tf.keras.utils.get_file('flower_photos', origin=dataset_url, untar=True)\n",
        "data_dir = pathlib.Path(data_dir)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VpmywIlsVrVx"
      },
      "source": [
        "다운로드 후, 데이터세트 사본을 사용할 수 있습니다. 총 3,670개의 이미지가 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "SbtTDYhOHZb6"
      },
      "outputs": [],
      "source": [
        "image_count = len(list(data_dir.glob('*/*.jpg')))\n",
        "print(image_count)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PVmwkOSdHZ5A"
      },
      "source": [
        "다음은 몇 가지 장미입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "N1loMlbYHeiJ"
      },
      "outputs": [],
      "source": [
        "roses = list(data_dir.glob('roses/*'))\n",
        "PIL.Image.open(str(roses[0]))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "RQbZBOTLHiUP"
      },
      "outputs": [],
      "source": [
        "PIL.Image.open(str(roses[1]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DGEqiBbRHnyI"
      },
      "source": [
        "그리고 일부 튤립이 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HyQkfPGdHilw"
      },
      "outputs": [],
      "source": [
        "tulips = list(data_dir.glob('tulips/*'))\n",
        "PIL.Image.open(str(tulips[0]))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wtlhWJPAHivf"
      },
      "outputs": [],
      "source": [
        "PIL.Image.open(str(tulips[1]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gIjgz7_JIo_m"
      },
      "source": [
        "# keras.preprocessing을 사용하여 로드하기\n",
        "\n",
        "유용한 [image_dataset_from_directory](https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/image_dataset_from_directory) 유틸리티를 사용하여 이러한 이미지를 디스크에서 로드하겠습니다. 몇 줄의 코드만으로 디스크의 이미지 디렉토리가 `tf.data.Dataset`로 가져와 집니다. 원하는 경우, [이미지 로드](https://www.tensorflow.org/tutorials/load_data/images) 튜토리얼을 방문하여 데이터 로드 코드를 처음부터 직접 작성할 수도 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xyDNn9MbIzfT"
      },
      "source": [
        "## 데이터세트 만들기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "anqiK_AGI086"
      },
      "source": [
        "로더에 대한 몇 가지 매개변수를 정의합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "H74l2DoDI2XD"
      },
      "outputs": [],
      "source": [
        "batch_size = 32\n",
        "img_height = 180\n",
        "img_width = 180"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pFBhRrrEI49z"
      },
      "source": [
        "모델을 개발할 때 검증 분할을 사용하는 것이 좋습니다. 훈련에 이미지의 80%를 사용하고 검증에 20%를 사용합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "fIR0kRZiI_AT"
      },
      "outputs": [],
      "source": [
        "train_ds = tf.keras.preprocessing.image_dataset_from_directory(\n",
        "  data_dir,\n",
        "  validation_split=0.2,\n",
        "  subset=\"training\",\n",
        "  seed=123,\n",
        "  image_size=(img_height, img_width),\n",
        "  batch_size=batch_size)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "iscU3UoVJBXj"
      },
      "outputs": [],
      "source": [
        "val_ds = tf.keras.preprocessing.image_dataset_from_directory(\n",
        "  data_dir,\n",
        "  validation_split=0.2,\n",
        "  subset=\"validation\",\n",
        "  seed=123,\n",
        "  image_size=(img_height, img_width),\n",
        "  batch_size=batch_size)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WLQULyAvJC3X"
      },
      "source": [
        "이러한 데이터세트의 `class_names` 속성에서 클래스 이름을 찾을 수 있습니다. 이들 클래스 이름은 알파벳 순서의 디렉토리 이름에 해당합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ZHAxkHX5JD3k"
      },
      "outputs": [],
      "source": [
        "class_names = train_ds.class_names\n",
        "print(class_names)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_uoVvxSLJW9m"
      },
      "source": [
        "## 데이터 시각화하기\n",
        "\n",
        "훈련 데이터세트의 처음 9개 이미지는 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wBmEA9c0JYes"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "plt.figure(figsize=(10, 10))\n",
        "for images, labels in train_ds.take(1):\n",
        "  for i in range(9):\n",
        "    ax = plt.subplot(3, 3, i + 1)\n",
        "    plt.imshow(images[i].numpy().astype(\"uint8\"))\n",
        "    plt.title(class_names[labels[i]])\n",
        "    plt.axis(\"off\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5M6BXtXFJdW0"
      },
      "source": [
        "잠시 후에 이들 데이터세트를 `model.fit`에 전달하여 모델을 훈련하겠습니다. 원하는 경우, 데이터세트를 수동으로 반복하고 이미지 배치를 검색할 수도 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2-MfMoenJi8s"
      },
      "outputs": [],
      "source": [
        "for image_batch, labels_batch in train_ds:\n",
        "  print(image_batch.shape)\n",
        "  print(labels_batch.shape)\n",
        "  break"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Wj4FrKxxJkoW"
      },
      "source": [
        "`image_batch`는 `(32, 180, 180, 3)` 형상의 텐서이며, `180x180x3` 형상의 32개 이미지 묶음으로 되어 있습니다(마지막 차원은 색상 채널 RGB를 나타냄). `label_batch`는 형상 `(32,)`의 텐서이며 32개 이미지에 해당하는 레이블입니다.\n",
        "\n",
        "`image_batch` 및 `labels_batch` 텐서에서 `.numpy()`를 호출하여 이를 `numpy.ndarray`로 변환할 수 있습니다.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4Dr0at41KcAU"
      },
      "source": [
        "## 성능을 높이도록 데이터세트 구성하기\n",
        "\n",
        "버퍼링된 프리페치를 사용하여 I/O를 차단하지 않고 디스크에서 데이터를 생성할 수 있도록 하겠습니다. 데이터를 로드할 때 다음 두 가지 중요한 메서드를 사용해야 합니다.\n",
        "\n",
        "`Dataset.cache()`는 첫 epoch 동안 디스크에서 이미지를 로드한 후 이미지를 메모리에 유지합니다. 이렇게 하면 모델을 훈련하는 동안 데이터세트가 병목 상태가 되지 않습니다. 데이터세트가 너무 커서 메모리에 맞지 않는 경우, 이 메서드를 사용하여 성능이 높은 온디스크 캐시를 생성할 수도 있습니다.\n",
        "\n",
        "`Dataset.prefetch()`는 훈련 중에 데이터 전처리 및 모델 실행과 겹칩니다.\n",
        "\n",
        "관심 있는 독자는 [데이터 성능 가이드](https://www.tensorflow.org/guide/data_performance#prefetching)에서 두 가지 방법과 디스크에 데이터를 캐싱하는 방법에 대해 자세히 알아볼 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "nOjJSm7DKoZA"
      },
      "outputs": [],
      "source": [
        "AUTOTUNE = tf.data.experimental.AUTOTUNE\n",
        "\n",
        "train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)\n",
        "val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8GUnmPF4JvEf"
      },
      "source": [
        "## 데이터 표준화하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e56VXHMWJxYT"
      },
      "source": [
        "RGB 채널 값은 `[0, 255]` 범위에 있습니다. 신경망에는 이상적이지 않습니다. 일반적으로 입력 값을 작게 만들어야 합니다. 여기서는 Rescaling 레이어를 사용하여 값이 `[0, 1]`에 있도록 표준화합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "PEYxo2CTJvY9"
      },
      "outputs": [],
      "source": [
        "normalization_layer = layers.experimental.preprocessing.Rescaling(1./255)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8aGpkwFaIw4i"
      },
      "source": [
        "참고: 이 섹션에서 소개하는 Keras Preprocesing 유틸리티와 레이어는 현재 실험적 단계이며 변경될 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Bl4RmanbJ4g0"
      },
      "source": [
        "이 레이어를 사용하는 방법에는 두 가지가 있습니다. map을 호출하여 데이터세트에 레이어를 적용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "X9o9ESaJJ502"
      },
      "outputs": [],
      "source": [
        "normalized_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))\n",
        "image_batch, labels_batch = next(iter(normalized_ds))\n",
        "first_image = image_batch[0]\n",
        "# Notice the pixels values are now in `[0,1]`.\n",
        "print(np.min(first_image), np.max(first_image)) "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XWEOmRSBJ9J8"
      },
      "source": [
        "또는 모델 정의 내에 레이어를 포함하여 배포를 단순화할 수 있습니다. 여기서는 두 번째 접근법을 사용할 것입니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XsRk1xCwKZR4"
      },
      "source": [
        "참고: 이전에 `image_dataset_from_directory`의 `image_size` 인수를 사용하여 이미지 크기를 조정했습니다. 모델에 크기 조정 논리를 포함하려면 [크기 조정](https://www.tensorflow.org/api_docs/python/tf/keras/layers/experimental/preprocessing/Resizing) 레이어를 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WcUTyDOPKucd"
      },
      "source": [
        "# 모델 만들기\n",
        "\n",
        "모델은 각각에 최대 풀 레이어가 있는 3개의 컨볼루션 블록으로 구성됩니다. 그 위에 `relu` 활성화 함수에 의해 활성화되는 128개의 단위가 있는 완전히 연결된 레이어가 있습니다. 이 모델은 높은 정확성을 고려해 조정되지 않았습니다. 이 튜토리얼의 목표는 표준적인 접근법을 보여주는 것입니다. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "QR6argA1K074"
      },
      "outputs": [],
      "source": [
        "num_classes = 5\n",
        "\n",
        "model = Sequential([\n",
        "  layers.experimental.preprocessing.Rescaling(1./255, input_shape=(img_height, img_width, 3)),\n",
        "  layers.Conv2D(16, 3, padding='same', activation='relu'),\n",
        "  layers.MaxPooling2D(),\n",
        "  layers.Conv2D(32, 3, padding='same', activation='relu'),\n",
        "  layers.MaxPooling2D(),\n",
        "  layers.Conv2D(64, 3, padding='same', activation='relu'),\n",
        "  layers.MaxPooling2D(),\n",
        "  layers.Flatten(),\n",
        "  layers.Dense(128, activation='relu'),\n",
        "  layers.Dense(num_classes)\n",
        "])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EaKFzz72Lqpg"
      },
      "source": [
        "## 모델 컴파일하기\n",
        "\n",
        "이 튜토리얼에서는 `optimizers.Adam` 옵티마이저 및 `losses.SparseCategoricalCrossentropy` 손실 함수를 선택합니다. 각 훈련 epoch에 대한 훈련 및 검증 정확성을 보려면 `metrics` 인수를 전달합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jloGNS1MLx3A"
      },
      "outputs": [],
      "source": [
        "model.compile(optimizer='adam',\n",
        "              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "              metrics=['accuracy'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aMJ4DnuJL55A"
      },
      "source": [
        "## 모델 요약\n",
        "\n",
        "모델의 `summary` 메서드를 사용하여 네트워크의 모든 레이어를 봅니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "llLYH-BXL7Xe"
      },
      "outputs": [],
      "source": [
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NiYHcbvaL9H-"
      },
      "source": [
        "## 모델 훈련하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5fWToCqYMErH"
      },
      "outputs": [],
      "source": [
        "epochs=10\n",
        "history = model.fit(\n",
        "  train_ds,\n",
        "  validation_data=val_ds,\n",
        "  epochs=epochs\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SyFKdQpXMJT4"
      },
      "source": [
        "## 훈련 결과 시각화하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dFvOvmAmMK9w"
      },
      "source": [
        "훈련 및 검증 세트에 대한 손실과 정확성 플롯을 생성합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jWnopEChMMCn"
      },
      "outputs": [],
      "source": [
        "acc = history.history['accuracy']\n",
        "val_acc = history.history['val_accuracy']\n",
        "\n",
        "loss=history.history['loss']\n",
        "val_loss=history.history['val_loss']\n",
        "\n",
        "epochs_range = range(epochs)\n",
        "\n",
        "plt.figure(figsize=(8, 8))\n",
        "plt.subplot(1, 2, 1)\n",
        "plt.plot(epochs_range, acc, label='Training Accuracy')\n",
        "plt.plot(epochs_range, val_acc, label='Validation Accuracy')\n",
        "plt.legend(loc='lower right')\n",
        "plt.title('Training and Validation Accuracy')\n",
        "\n",
        "plt.subplot(1, 2, 2)\n",
        "plt.plot(epochs_range, loss, label='Training Loss')\n",
        "plt.plot(epochs_range, val_loss, label='Validation Loss')\n",
        "plt.legend(loc='upper right')\n",
        "plt.title('Training and Validation Loss')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hO_jT7HwMrEn"
      },
      "source": [
        "플롯에서 볼 수 있듯이 훈련 정확성과 검증 정확성은 큰 차이가 있으며, 모델은 검증 세트에서 약 60%의 정확성까지만 도달합니다.\n",
        "\n",
        "무엇이 잘못되었는지 살펴보고 모델의 전반적인 성능을 향상해 보겠습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hqtyGodAMvNV"
      },
      "source": [
        "## 과대적합"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ixsz9XFfMxcu"
      },
      "source": [
        "위의 플롯에서 훈련 정확성은 시간이 지남에 따라 선형적으로 증가하는 반면, 검증 정확성은 훈련 과정에서 약 60%를 벗어나지 못합니다. 또한 훈련 정확성과 검증 정확성 간의 정확성 차이가 상당한데, 이는 [과대적합](https://www.tensorflow.org/tutorials/keras/overfit_and_underfit)의 징후입니다.\n",
        "\n",
        "훈련 예제가 적을 때 모델은 새로운 예제에서 모델의 성능에 부정적인 영향을 미치는 정도까지 훈련 예제의 노이즈나 원치 않는 세부까지 학습합니다. 이 현상을 과대적합이라고 합니다. 이는 모델이 새 데이터세트에서 일반화하는 데 어려움이 있음을 의미합니다.\n",
        "\n",
        "훈련 과정에서 과대적합을 막는 여러 가지 방법들이 있습니다. 이 튜토리얼에서는 *데이터 증강*을 사용하고 모델에 *드롭아웃*을 추가합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BDMfYqwmM1C-"
      },
      "source": [
        "## 데이터 증강"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GxYwix81M2YO"
      },
      "source": [
        "과대적합은 일반적으로 훈련 예제가 적을 때 발생합니다. [데이터 증강](https://www.tensorflow.org/tutorials/images/data_augmentation)은 증강한 다음 믿을 수 있는 이미지를 생성하는 임의 변환을 사용하는 방법으로 기존 예제에서 추가 훈련 데이터를 생성하는 접근법을 취합니다. 그러면 모델이 데이터의 더 많은 측면을 파악하게 되므로 일반화가 더 쉬워집니다.\n",
        "\n",
        "여기서는 실험적인 [Keras 전처리 레이어](https://www.tensorflow.org/api_docs/python/tf/keras/layers/experimental/preprocessing/?version=nightly)를 사용하여 데이터 증강을 구현합니다. 이들 레이어는 다른 레이어와 마찬가지로 모델 내에 포함될 수 있으며, GPU에서 실행됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9J80BAbIMs21"
      },
      "outputs": [],
      "source": [
        "data_augmentation = keras.Sequential(\n",
        "  [\n",
        "    layers.experimental.preprocessing.RandomFlip(\"horizontal\", \n",
        "                                                 input_shape=(img_height, \n",
        "                                                              img_width,\n",
        "                                                              3)),\n",
        "    layers.experimental.preprocessing.RandomRotation(0.1),\n",
        "    layers.experimental.preprocessing.RandomZoom(0.1),\n",
        "  ]\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PN4k1dK3S6eV"
      },
      "source": [
        "동일한 이미지에 데이터 증강을 여러 번 적용하여 몇 가지 증강된 예제가 어떻게 보이는지 시각화하겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7Z90k539S838"
      },
      "outputs": [],
      "source": [
        "plt.figure(figsize=(10, 10))\n",
        "for images, _ in train_ds.take(1):\n",
        "  for i in range(9):\n",
        "    augmented_images = data_augmentation(images)\n",
        "    ax = plt.subplot(3, 3, i + 1)\n",
        "    plt.imshow(augmented_images[0].numpy().astype(\"uint8\"))\n",
        "    plt.axis(\"off\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tsjXCBLYYNs5"
      },
      "source": [
        "잠시 후에 데이터 증강을 사용하여 모델을 훈련하겠습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZeD3bXepYKXs"
      },
      "source": [
        "## 드롭아웃\n",
        "\n",
        "과대적합을 줄이는 또 다른 기술은 *정규화*의 한 형태인 [드롭아웃](https://developers.google.com/machine-learning/glossary#dropout_regularization)을 네트워크에 도입하는 것입니다.\n",
        "\n",
        "드롭아웃을 레이어에 적용하면, 훈련 프로세스 중에 레이어에서 여러 출력 단위가 무작위로 드롭아웃됩니다(활성화를 0으로 설정). 드롭아웃은 0.1, 0.2, 0.4 등의 형식으로 소수를 입력 값으로 사용합니다. 이는 적용된 레이어에서 출력 단위의 10%, 20% 또는 40%를 임의로 제거하는 것을 의미합니다.\n",
        "\n",
        "`layers.Dropout`을 사용하여 새로운 신경망을 생성한 다음, 증강 이미지를 사용하여 훈련해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2Zeg8zsqXCsm"
      },
      "outputs": [],
      "source": [
        "model = Sequential([\n",
        "  data_augmentation,\n",
        "  layers.experimental.preprocessing.Rescaling(1./255),\n",
        "  layers.Conv2D(16, 3, padding='same', activation='relu'),\n",
        "  layers.MaxPooling2D(),\n",
        "  layers.Conv2D(32, 3, padding='same', activation='relu'),\n",
        "  layers.MaxPooling2D(),\n",
        "  layers.Conv2D(64, 3, padding='same', activation='relu'),\n",
        "  layers.MaxPooling2D(),\n",
        "  layers.Dropout(0.2),\n",
        "  layers.Flatten(),\n",
        "  layers.Dense(128, activation='relu'),\n",
        "  layers.Dense(num_classes)\n",
        "])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "L4nEcuqgZLbi"
      },
      "source": [
        "## 모델 컴파일 및 훈련하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "EvyAINs9ZOmJ"
      },
      "outputs": [],
      "source": [
        "model.compile(optimizer='adam',\n",
        "              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "              metrics=['accuracy'])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wWLkKoKjZSoC"
      },
      "outputs": [],
      "source": [
        "model.summary()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LWS-vvNaZDag"
      },
      "outputs": [],
      "source": [
        "epochs = 15\n",
        "history = model.fit(\n",
        "  train_ds,\n",
        "  validation_data=val_ds,\n",
        "  epochs=epochs\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Lkdl8VsBbZOu"
      },
      "source": [
        "## 훈련 결과 시각화하기\n",
        "\n",
        "데이터 증강 및 드롭아웃을 적용한 후, 이전보다 과대적합이 줄어들고 훈련 및 검증 정확성이 더 가깝게 조정됩니다. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dduoLfKsZVIA"
      },
      "outputs": [],
      "source": [
        "acc = history.history['accuracy']\n",
        "val_acc = history.history['val_accuracy']\n",
        "\n",
        "loss = history.history['loss']\n",
        "val_loss = history.history['val_loss']\n",
        "\n",
        "epochs_range = range(epochs)\n",
        "\n",
        "plt.figure(figsize=(8, 8))\n",
        "plt.subplot(1, 2, 1)\n",
        "plt.plot(epochs_range, acc, label='Training Accuracy')\n",
        "plt.plot(epochs_range, val_acc, label='Validation Accuracy')\n",
        "plt.legend(loc='lower right')\n",
        "plt.title('Training and Validation Accuracy')\n",
        "\n",
        "plt.subplot(1, 2, 2)\n",
        "plt.plot(epochs_range, loss, label='Training Loss')\n",
        "plt.plot(epochs_range, val_loss, label='Validation Loss')\n",
        "plt.legend(loc='upper right')\n",
        "plt.title('Training and Validation Loss')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dtv5VbaVb-3W"
      },
      "source": [
        "## 새로운 데이터로 예측하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "10buWpJbcCQz"
      },
      "source": [
        "마지막으로, 모델을 사용하여 훈련 또는 검증 세트에 포함되지 않은 이미지를 분류해 보겠습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NKgMZ4bDcHf7"
      },
      "source": [
        "참고: 데이터 증강 및 드롭아웃 레이어는 추론 시 비활성화됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dC40sRITBSsQ"
      },
      "outputs": [],
      "source": [
        "sunflower_url = \"https://storage.googleapis.com/download.tensorflow.org/example_images/592px-Red_sunflower.jpg\"\n",
        "sunflower_path = tf.keras.utils.get_file('Red_sunflower', origin=sunflower_url)\n",
        "\n",
        "img = keras.preprocessing.image.load_img(\n",
        "    sunflower_path, target_size=(img_height, img_width)\n",
        ")\n",
        "img_array = keras.preprocessing.image.img_to_array(img)\n",
        "img_array = tf.expand_dims(img_array, 0) # Create a batch\n",
        "\n",
        "predictions = model.predict(img_array)\n",
        "score = tf.nn.softmax(predictions[0])\n",
        "\n",
        "print(\n",
        "    \"This image most likely belongs to {} with a {:.2f} percent confidence.\"\n",
        "    .format(class_names[np.argmax(score)], 100 * np.max(score))\n",
        ")"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "classification.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
